from django.db import models
from betterspace.courses.models import Course
from betterspace.courses.apps import App, course_app
from settings import PROJECT_PATH
import os

import sys
print
print "************ LOL, THE FILES APP IS BEING IMPORTED *************"
print "...and sys.path is '%s'..." % sys.path

FILES_ROOT = os.path.join(PROJECT_PATH, 'courses/files/files_root')

DEFAULT_DIRS = {'Assignments':
                    {'Readings':None,
                     'Homework':None},
                'Syllabus':None,
                'Cool stuff':None,
               }


class Node(object):
    """
    Either a directory or a file.
    """
    class DoubleDotError(StandardError):
        pass
    
    def __init__(self,path, *extra):
        """
        You can call any Node with a list of paths to be joined. For example,
        
        Node('lol','wtf/','/dood','/')
        
        is equivalent to
        
        Node('lol/wtf/dood/')
        
        Also, Node objects are castable to any subclass. e.g.
        
        >>> n = Node('lol/wtf')
        >>> n
        <Node "lol/wtf">
        >>> Directory(n)
        <Directory "lol/wtf">
        >>> FileNode(n)
        <FileNode "lol/wtf">
        """
        # allows for casting
        if isinstance(path, Node):
            path = path.path
        
        self.path = path    
        for e in extra:
            self.path = os.path.join(self.path,e)
            
        self.abspath = os.path.normpath(os.path.join(FILES_ROOT,self.path))
        self.path = os.path.relpath(self.abspath, FILES_ROOT)
        
        assert self.abspath.startswith(FILES_ROOT), DoubleDotError(
            'Oops, it looks like we doubledotted out of FILES_ROOT!'
        )
        
    def __unicode__(self):
        return self.path
        
    def __str__(self):
        return self.path
    
    def __repr__(self):
        return '<Node "%s">' % self
    
    def __eq__(self, other):
        return self.abspath == other.abspath
    
    @property
    def parent(self):
        return Directory(os.path.dirname(self.path))

    @property
    def name(self):
        return os.path.basename(self.path)        
    
    display_name = name
    
    @property
    def html(self):
        return '<a href="%s">%s</a>' % (self.url, self.display_name)
    
    
    def mv(self, loc, *extra):
        """
        Use node.mv('path/to/new/location'), relative to self.parent.abspath.
        """
        print 'running mv for node %s...' % self
        if isinstance(loc, Node):
            loc = loc.abspath
        
        if os.path.isabs(loc):
            loc = os.path.splitdrive(loc)[1:] #just for windoaze.
            doc_root = self.course_root
        
        newpath = os.path.normpath(os.path.join(self.parent.abspath, loc))
        print 'the new path is "%s"' % newpath
        
        if not newpath.startswith(self.course_root.abspath):
            raise DoubleDotError("""
                Oops, you tried to rename %s to "%s", but that is outside the course_root %s."""
                % (self, newpath, course_root)
            )

        print 'attempting to rename the file on disk...',
        try:        
            os.renames(self.abspath, newpath)
            print 'success!'
        except OSError:
            if self.exists:
                raise #only raise OSError if the file does exist, but we can't move it.
        print
        
        print 'running __init__...'
        self.__init__(newpath) #rename the object.
        print 'path = "%s"' % self.path
        print 'abspath = "%s"' % self.path
        print 'url = "%s"' % self.url
        print 'done with mv!'
    
    @property
    def is_dir(self):
        return os.path.isdir(self.abspath)
    
    @property
    def is_file(self):
        return os.path.isfile(self.abspath)
    
    def check_link(self):
        if not os.path.splitext(self.name)[1] == LinkNode.LINK_EXT:
            return False
        if not self.is_file:
            return False
        with self.open() as f:
            contents = f.read()
        try:
            LinkNode.parse_contents(contents)
            return True
        except LinkNode.LinkError:
            return False
            
    @property
    def exists(self):
        return os.path.exists(self.abspath)

    @property
    def course(self):
        try:
            instructor, course_name = self.path.split(os.sep)[0:2]
            return Course.getByNames(instructor=instructor, name=course_name)
        except Course.DoesNotExist:
            return None
    
    @property
    def course_root(self):
        instructor, course = self.path.split(os.sep)[0:2]
        return Directory(instructor, course)
    
    @property
    def url(self):
        return os.path.join('/',self.path)
    
    @staticmethod
    def zip_files(file_list, flat=False, doc_root=''):
        import zipfile
        z = zipfile.ZipFile(zip_to, mode='w')
        print file_list
        
        if isinstance(doc_root, str):
            doc_root = Directory(doc_root) #doc_root had better be a directory
        
        for f in file_list:
            if f.is_dir:
                zip_files(f, z, doc_root=doc_root) 
                #remember, Directory's are iterable
                continue
            
            f = FileNode(f) #it's not a directory, so cast to FileNode
            
            if flat:
                arcname = f.name
            else:
                if doc_root == '':
                    arcname=f.path
                else:
                    arcname=os.path.relpath(f.path, doc_root)
            
            z.write(f.abspath, arcname=arcname)        
        
        return z


class Directory(Node):
       
    @property
    def child_nodes(self):
        """
        Returns a list of the child nodes of the current node.  If it's a file, it'll be 
        None.  If it's an empty directory, it'll be [].
        """
        nodes = []
        for name in os.listdir(self.abspath):
            n = Node(self.path,name)
            if n.is_dir:
                nodes.append(Directory(n))
            else:
                nodes.append(FileNode(n))
        return nodes
                
    def __iter__(self):
        """
        This allows you to say
        >>> for thing in dir:
        ...     #do something
        """
        for thing in self.child_nodes:
            yield thing
    
    def __len__(self):
        return len(self.child_nodes)
    
    def __getitem__(self,i):
        if isinstance(i, int):
            return self.child_nodes[i]
        elif isinstance(i, str):
            for node in self:
                if node.name == i:
                    return node
            raise KeyError("No node was found in %s with name %s" % (self, i))
        raise KeyError('Only int and str types are supported for Directory.__getitem__()')
    
    def __div__(self, thing):
        """
        If d is a Directory, then d/'lol.txt' will be the FileNode for lol.txt.
        AMAZING.
        """
        return self[thing]
        
    def __repr__(self):
        return '<Directory "%s">' % self
    
    def get_file_paths(self):
        nodes = []
        for path, narf, names in os.walk(self.abspath):
            for name in names:
                nodes.append(os.path.join(path, name))
        return nodes
    
    def all(self):
        return [FileNode(path) for path in self.get_file_paths()]

    get_files = all
    
    def zip_contents(self, zip_to):
        return Node.zip_files(self.get_files, doc_root=self)
    
    def save(self):
        try:
            os.makedirs(self.abspath)
        except OSError:
            pass
    
    def delete(self):
        """
        Deletes the directory from the filesystem and unreferences the variable.
        
        WARNING: THIS DELETES A TON OF SHIT.  BE CAREFUL.
        """
        #print 'LOL!'
        for root, dirs, files in os.walk(self.abspath, topdown=False):
            #print 'WTF! (%s) (%s) (%s)' % (root,dirs,files)
            for name in files:
                os.remove(os.path.join(root, name))
            for name in dirs:
                print 'removing ', os.path.join(root,name)
                os.rmdir(os.path.join(root, name))
        os.rmdir(self.abspath)
        del self
    

class FileNode(Node):
    
    def __repr__(self):
        return '<FileNode "%s">' % self
    
    def open(self, mode='r'):
        return open(self.abspath, mode)
    
    def save(self,contents=''):
        """
        WARNING: THIS WILL OVERWRITE WHATEVER YOU HAD THERE.
        Make SURE to check authentication before calling this method.
        It also doesn't check for doubledotting.
        Except for internals, use CourseFileTree.save_file(path,contents) instead
        """
        Directory(self.parent.path).save()
        with open(self.abspath,'w') as f:
            f.write(contents)
    
    @property
    def app(self):
        try:
            return FileMeta.objects.get(path=self.path).app
        except FileMeta.DoesNotExist:
            return None
    
    def stat(self):
        return os.stat(self.abspath)
        
    def last_modified(self):
        """Returns Unix absolute time of the last modification"""
        return self.stat()[9]

class LinkNode(FileNode):
    """
    For weblinks-as-files.
    The file should be named something like "Here's a cool site.weblink", and its
    contents should be a single line with the url, like
    
    '^http://www.cool.site.com/something?something=somethingelse$'
    
    The only processing done on this url is that the whitespace is stripped from it.
    This would be translated into
    
    <a href="http://www.cool.site.com/something">Here's a cool site</a>
    
    This code will be accessible through self.html.
    """
    import urllib
    
    class LinkError(StandardError):
        pass
    
    LINK_EXT = '.html'
    
    HTML_PREFIX = '''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="refresh" content="0;url='''
    
    HTML_SUFFIX = '''"></head>
<body></body>
</html>'''
    
    def read_url(self):
        with self.open() as f:
            contents = f.read().strip()
        
        return self.parse_contents(contents)
        
        
    def __init__(self, path, *args, **kwargs):
        
        url = kwargs.get('url', None)

        if url is None and getattr(self, 'url', None) is None:
            try:
                self.url = self.read_url()
            except OSError:
                raise KeyError('LinkNode was initiated without a url keyword or a record on disk!')
        else:
            self.url = url
        
        args = [path] + list(args)
        
        tail = args[-1] if args else path
        
        tail = tail.tail if isinstance(tail, Node) else tail
        #add the link file extension
        print 'my tail is "%s"' % tail
        if not tail.endswith(self.LINK_EXT):
            print 'my tail doesn\'t end with LINK_EXT.'
            ext = self.LINK_EXT
            if tail.endswith('.'):
                ext = LINK_EXT[1:]
            args[-1] = tail + ext
            print 'my new args are: %s' % args
            #this will rename the file on disk, if it exists.
        
        super(LinkNode, self).__init__(*args)

    
    @classmethod
    def parse_contents(cls, contents):
        if not contents.startswith(cls.HTML_PREFIX):
            raise cls.LinkError('''Oops.
Your file doesn\'t start with HTML_PREFIX.  Dumping file...
%s''' % contents)
        
        return contents[len(cls.HTML_PREFIX):-len(cls.HTML_SUFFIX)]
    
    def save(self, url=None):
        print 'attempting to save LinkNode %s' % self
        self.url = url if url is not None else self.url
        
        contents = self.HTML_PREFIX + self.url + self.HTML_SUFFIX
        
        super(LinkNode, self).save(contents=contents)
    
    def __repr__(self):
        return '<LinkNode at "%s" pointing to "%s">' % (self.path, self.url)
            
    @property
    def display_name(self):
        """
        This is the display name, which is just the filename without the link extension.
        """
        return os.path.splitext(self.name)[0]

    @property
    def encoded_url(self):
        return urllib.urlencode(self.url)
    
    #make self.url act like a regular attribute again.
    def _seturl(self, value):
        self._url = value
    
    def _delurl(self):
        del self._url
        
    url = property(
        lambda self: self._url, #getter
        _seturl, #setter
        _delurl #deleter
    )
         
@course_app
class CourseFileTree(Directory):
    slug = 'files'
    files_tab = App.Tab(slug='files', title='Files')
    
    def __repr__(self):
        return '<CourseFileTree "%s">' % self
    
    def __init__(self, course):
        self._course = course
        super(CourseFileTree, self).__init__( 
            self._course.instructor.user.username, 
            self._course.slug
        )
        
    def saveFile(self,path,contents, app=None):
        """
        Use this one instead of FileNode.save().  It checks for doubledotting.
        path is relative to self.abspath
        """   

        d = FileNode(self.path,path)
        
        #check if we've doubledotted out of self.root.  Remember Node.__init__ normalizes paths.
        if not d.abspath.startswith(self.abspath):
            raise DoubleDotError('Oops, the course "%s" is not allowed \
                to write to "%s".  The path was supposed to start with "%s".  \
                Tame your double-dots!' % (self.course, 
                d.abspath, self.abspath)
            )
                
        d.save(contents)
        
        FileMeta(app=app, path=d.path).save()
    
    def _load_dirs_by_dict(self, d, rel=None):
        if d is None:
            return None
        if rel is None:
            rel = self.path
        for f in d.keys():
            Directory(rel,f).save()
            self._load_dirs_by_dict(d[f],os.path.join(rel,f)) #yay recursion
        return None
    
    def load_defaults(self): #use this instead of _load_dirs_by_dict.  
        self._load_dirs_by_dict(DEFAULT_DIRS)
    
    def getByApp(self, a):
        if isinstance(a, str):
            from courses.models import App
            a = App.objects.get(slug=a)
        return [FileNode(x.path) for x in
            FileMeta.objects.filter(
                path__startswith=self.path, app=a
            )
        ]

#The only model in this app!  Wowz!

class FileMeta(models.Model):
    from courses.models import App
    
    #relative to FILES_ROOT
    path = models.FilePathField(recursive=True, path=FILES_ROOT, unique=True, 
        blank=True, null=True)
    
    #app = models.ForeignKey(App)
    #TODO:  How does this fit in to the new Features/Tabs thing?
    #App is not a model anymore.
    
    
#    def __unicode__(self):
#        return 'Path %s was created by %s.' % (self.path, self.app)

